package com.aphrodite.util;

import java.util.Enumeration;

/**
 * Vector to store integer primitives.
 * <p>
 * Integer primitives and its subset can be used
 * </p>
 */
public class IntVector {
	/**
	 * Convert an integer array into a IntVector
	 * 
	 * @param array
	 *            To convert
	 * @return Copy of the given <code>IntVector</code>
	 */
	public static IntVector toIntVector(int[] array) {
		IntVector vector = new IntVector(array.length);
		for (int i = 0; i < array.length; i++) {
			vector.addElement(array[i]);
		}
		return vector;
	}

	// /*
	// * (non-Javadoc)
	// *
	// * @see de.enough.polish.io.Externalizable#read(java.io.DataInputStream)
	// */
	// public void read(DataInputStream in) throws IOException {
	// int storeSize = in.readInt();
	// int growFactor = in.readInt();
	// int[] store = new int[storeSize];
	// for (int i = 0; i < store.length; i++) {
	// store[i] = in.readInt();
	// }
	// this.storedObjects = store;
	// this.size = storeSize;
	// this.growthFactor = growFactor;
	// }
	//
	// /*
	// * (non-Javadoc)
	// *
	// * @see de.enough.polish.io.Externalizable#write(java.io.DataOutputStream)
	// */
	// public void write(DataOutputStream out) throws IOException {
	// out.writeInt(this.size);
	// out.writeInt(this.growthFactor);
	// for (int i = 0; i < this.size; i++) {
	// int o = this.storedObjects[i];
	// out.writeInt(o);
	// }
	// }
	private int[] data;
	private int growthCount;

	private int size;

	private int total;

	/**
	 * Creates a Vector with the initial capacity of 10 and a growth factor of
	 * 75%
	 */
	public IntVector() {
		this(10, 75);
	}

	/**
	 * Creates a Vector with the given initial capacity and a growth factor of
	 * 75%
	 * 
	 * @param initialCapacity
	 *            the capacity of this vector.
	 */
	public IntVector(int initialCapacity) {
		this(initialCapacity, 75);
	}

	/**
	 * Creates a new Integer vector
	 * 
	 * @param initialCapacity
	 *            the capacity of this array vector.
	 * @param growthFactor
	 *            the factor in % for increasing the capacity when there's not
	 *            enough room in this vector anymore
	 */
	public IntVector(int initialCapacity, int growthFactor) {
		this.data = new int[initialCapacity];
		this.growthCount = growthFactor;
	}

	/**
	 * Stores the given element in this vector.
	 * 
	 * @param element
	 *            the element which should be appended to this vector.
	 * @see #addElementAt(int, int )
	 */
	public void addElement(int element) {
		if (this.size >= this.data.length) {
			increaseCapacity();
		}
		this.data[this.size] = element;
		this.size++;
	}

	/**
	 * Inserts the given element at the defined position. Any following elements
	 * are shifted one position to the back.
	 * 
	 * @param index
	 *            the position at which the element should be inserted, use 0
	 *            when the element should be inserted in the front of this
	 *            vector.
	 * @param element
	 *            the element which should be inserted
	 * @throws IndexOutOfBoundsException
	 *             when the index < 0 || index >= size()
	 */
	public void addElementAt(int index, int element) {
		if (index < 0 || index > this.size) {
			throw new IndexOutOfBoundsException("the index [" + index
					+ "] is not valid for this vector with the size [" + this.size + "].");
		}
		if (this.size >= this.data.length) {
			increaseCapacity();
		}
		// shift all following elements one position to the back:
		for (int i = this.size; i > index; i--) {
			this.data[i] = this.data[i - 1];
		}
		// insert the given element:
		this.data[index] = element;
		this.size++;
	}

	/**
	 * Add an array of integers to this vector
	 * 
	 * @param elements
	 */
	public void addElements(int[] elements) {
		for (int i = 0; i < elements.length; i++) {
			this.addElement(elements[i]);
		}
	}

	/**
	 * Removes all of the elements from this vector. The vector will be empty
	 * after this call returns.
	 */
	public void clear() {
		for (int i = 0; i < size; i++) {
			data[i] = 0;
		}
		this.size = 0;
	}

	/**
	 * Determines whether the given element is stored in this vector.
	 * 
	 * @param element
	 *            the element which might be stored in this vector
	 * @return true when the given element is stored in this vector
	 * @see #removeElement(int)
	 */
	public boolean contains(int element) {
		for (int i = 0; i < this.size; i++) {
			int object = this.data[i];
			if (object == element) {
				return true;
			}
		}
		return false;
	}

	public void copyInto(int[] array) {
		System.arraycopy(this.data, 0, array, 0, this.size);
	}

	/**
	 * Returns the element at the specified position in this vector.
	 * 
	 * @param index
	 *            the position of the desired element.
	 * @return the element stored at the given position
	 * @throws IndexOutOfBoundsException
	 *             when the index < 0 || index >= size()
	 */
	public int elementAt(int index) {
		if (index < 0 || index >= this.size) {
			throw new IndexOutOfBoundsException("the index [" + index
					+ "] is not valid for this vector with the size [" + this.size + "].");
		}
		return this.data[index];
	}

	/**
	 * Returns an enumeration of the Integer wrapper type
	 * 
	 * @return IntVector Enumeration
	 */
	public Enumeration elements() {
		total = 0;
		return new Enumeration() {
			public boolean hasMoreElements() {
				return total < size;
			}

			public Object nextElement() {
				if (total < size) {
					Integer in = new Integer(elementAt(total));
					total++;
					return in;
				}
				return null;
			}
		};
	}

	/**
	 * Retrieves the internal array - use with care! This method allows to
	 * access stored objects without creating an intermediate array. You really
	 * should refrain from changing any elements in the returned array unless
	 * you are 110% sure about what you are doing. It is safe to cycle through
	 * this array to access it's elements, though. Note that some array
	 * positions might contain null. Also note that the internal array is
	 * changed whenever this vector has to be increased.
	 * 
	 * @return the internal array
	 */
	public int[] getInternalArray() {
		return this.data;
	}

	/**
	 * increases the capacity of this vector.
	 */
	private void increaseCapacity() {
		int currentCapacity = this.data.length;
		int newCapacity = currentCapacity + ((currentCapacity * this.growthCount) / 100);
		if (newCapacity == currentCapacity) {
			newCapacity++;
		}
		int[] newStore = new int[newCapacity];
		System.arraycopy(this.data, 0, newStore, 0, this.size);
		this.data = newStore;
	}

	/**
	 * Retrieves the index of the given object.
	 * 
	 * @param element
	 *            the int which is part of this vector.
	 * @return the index of the object or -1 when the object is not part of this
	 *         vector.
	 */
	public int indexOf(int element) {
		for (int i = 0; i < this.size; i++) {
			int object = this.data[i];
			if (object == element) {
				return i;
			}
		}
		return -1;
	}

	public boolean isEmpty() {
		return size == 0;
	}

	/**
	 * Returns the index of the last occurrence of the specified integer in this
	 * vector.
	 * 
	 * @param element
	 *            to be found.
	 * @return The last index of the element. -1 if the element was not found.
	 */
	public int lastIndexOf(int element) {
		int lastIndex = -1;
		for (int i = 0; i < size; i++) {
			if (data[i] == element) {
				lastIndex = i;
			}
		}
		return lastIndex;
	}

	/**
	 * Removes the given element.
	 * 
	 * @param element
	 *            the element which should be removed.
	 * @return true when the element was found in this vector.
	 * @see #contains(int)
	 */
	public boolean removeElement(int element) {
		int index = -1;
		for (int i = 0; i < this.size; i++) {
			int object = this.data[i];
			if (object == element) {
				index = i;
				break;
			}
		}
		if (index == -1) {
			return false;
		}
		for (int i = index + 1; i < this.size; i++) {
			this.data[i - 1] = this.data[i];
		}
		this.size--;
		return true;
	}

	/**
	 * Removes the element at the specified position in this vector.
	 * 
	 * @param index
	 *            the position of the desired element.
	 * @return the element stored at the given position
	 * @throws IndexOutOfBoundsException
	 *             when the index < 0 || index >= size()
	 */
	public int removeElementAt(int index) {
		if (index < 0 || index >= this.size) {
			throw new IndexOutOfBoundsException("the index [" + index
					+ "] is not valid for this vector with the size [" + this.size + "].");
		}
		int removed = this.data[index];
		for (int i = index + 1; i < this.size; i++) {
			this.data[i - 1] = this.data[i];
		}
		this.size--;
		return removed;
	}

	/**
	 * Replaces the element at the specified position in this vector with the
	 * specified element.
	 * 
	 * @param index
	 *            the position of the element, the first element has the index
	 *            0.
	 * @param element
	 *            the element which should be set
	 * @return the replaced element
	 * @throws IndexOutOfBoundsException
	 *             when the index < 0 || index >= size()
	 */
	public int set(int index, int element) {
		if (index < 0 || index >= this.size) {
			throw new IndexOutOfBoundsException("the index [" + index
					+ "] is not valid for this vector with the size [" + this.size + "].");
		}
		int replaced = this.data[index];
		this.data[index] = element;
		return replaced;
	}

	/**
	 * Retrieves the current size of this array vector.
	 * 
	 * @return the number of stored elements in this vector.
	 */
	public int size() {
		return this.size;
	}

	/**
	 * Returns all stored elements as an array.
	 * 
	 * @return the stored elements as an array.
	 */
	public int[] toArray() {
		int[] copy = new int[this.size];
		copyInto(copy);
		// System.arraycopy(this.storedObjects, 0, copy, 0, this.size);
		return copy;
	}

	/**
	 * Returns String containing the String representations of all objects of
	 * this vector.
	 * 
	 * @return the stored elements in a String representation.
	 */
	public String toString() {
		StringBuffer buffer = new StringBuffer(this.size * 2);
		buffer.append(super.toString()).append("{\n");
		for (int i = 0; i < this.size; i++) {
			buffer.append(this.data[i]);
			buffer.append('\n');
		}
		buffer.append('}');
		return buffer.toString();
	}

	/**
	 * Trims the capacity of this vector instance to be the vector's current
	 * size. An application can use this operation to minimize the storage of a
	 * vector instance.
	 */
	public void trimToSize() {
		if (this.data.length != this.size) {
			int[] newStore = new int[this.size];
			System.arraycopy(this.data, 0, newStore, 0, this.size);
			this.data = newStore;
		}
	}

}
